package top.eggcode.component.query;

import com.alibaba.fastjson.JSON;
import lombok.Data;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.hibernate.Session;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.eggcode.common.exception.ApplicationException;
import top.eggcode.common.model.Pageable;
import top.eggcode.component.query.canst.JoinType;
import top.eggcode.component.query.clause.*;
import top.eggcode.component.query.model.Expression;
import top.eggcode.component.query.model.PageList;
import top.eggcode.component.query.predicate.CompoundPredicate;
import top.eggcode.component.query.predicate.Predicate;
import top.eggcode.component.query.util.CaseFormat;

import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.*;
import java.util.function.Consumer;

/**
 * Title: 查询包装器
 * Description: TODO
 * Date: 2021/8/21 23:54
 *
 * @author JiaQi Ding
 * @version 1.0
 */
@Data
public class QueryWrapper<T> {
    private final CriteriaBuilder builder;

    private final Class<T> entityType;

    private Logger logger = LoggerFactory.getLogger(QueryWrapper.class);

    private String mainSentence;

    private boolean lockSentence;

    private SelectClause selectClause;

    private List<JoinClause> joinTables;

    private List<Expression<?>> parameters;

    private List<String> havingConditions;

    private FromClause fromClause;

    private WhereClause whereClause;

    private CompoundPredicate wherePredicate;

    private JoinClause joinClause;

    private OnClause onClause;

    private GroupByClause groupByCLause;

    private OrderByClause orderByClause;

    private LimitClause limitClause;

    private OffsetClause offsetClause;


    public QueryWrapper(CriteriaBuilder builder, Class<T> returnClass) {
        this.lockSentence = false;
        this.builder = builder;
        this.entityType = returnClass;
        this.parameters = new LinkedList<>();
        this.joinTables = new LinkedList<>();
    }

    public QueryWrapper<T> selectAll(String alias) {
        this.selectClause = new SelectClause();
        FieldUtils.getAllFieldsList(entityType).forEach(targetField -> {

            Transient anno = targetField.getAnnotation(Transient.class);
            if (anno == null) {
                String attributeName = targetField.getName();
                this.selectClause.add(alias + "."
                        + com.google.common.base.CaseFormat.LOWER_CAMEL.to(com.google.common.base.CaseFormat.LOWER_UNDERSCORE, attributeName)
                        + " AS "
                        + attributeName);
            }

        });
        return this;
    }

    public QueryWrapper<T> selectEntity() {
        this.selectClause = new SelectClause();
        FieldUtils.getAllFieldsList(entityType).forEach(targetField -> {

            Transient anno = targetField.getAnnotation(Transient.class);
            if (anno == null) {
                String attributeName = targetField.getName();
                this.selectClause.add(com.google.common.base.CaseFormat.LOWER_CAMEL.to(com.google.common.base.CaseFormat.LOWER_UNDERSCORE, attributeName));
            }

        });
        return this;
    }

    public QueryWrapper<T> selectEntity(String alias) {
        if (this.selectClause == null) {
            this.selectClause = new SelectClause();
        }
        FieldUtils.getAllFieldsList(entityType).forEach(targetField -> {

            Transient anno = targetField.getAnnotation(Transient.class);
            if (anno == null) {
                String attributeName = targetField.getName();
                this.selectClause.add(alias + "." + CaseFormat.toUnderscore(attributeName));
            }

        });
        return this;
    }

    public QueryWrapper<T> select() {
        this.selectClause = new SelectClause();
        return this;
    }

    public QueryWrapper<T> select(String... columns) {
        this.selectClause = new SelectClause();
        this.selectClause.add(Arrays.asList(columns));
        return this;
    }

    /**
     * “name as n, age, weight as w”
     *
     * @param columnObject 宾语
     */
    public QueryWrapper<T> select(String columnObject) {
        this.selectClause = new SelectClause();
        String[] columns = columnObject.split(",");
        for (String columnName : columns) {
            this.selectClause.add(columnName.trim());
        }
        return this;
    }

    public QueryWrapper<T> from(String tableName) {
        this.fromClause = new FromClause(tableName);
        return this;
    }

    public QueryWrapper<T> fromEntity() {

        Table anno = entityType.getAnnotation(Table.class);
        if (anno == null) {
            throw new ApplicationException("实体类" + entityType.getName() + " 未设置 @Table 注解");
        }
        String name = anno.name();
        this.fromClause = new FromClause(name);

        this.selectEntity();

        return this;
    }

    public QueryWrapper<T> fromEntity(String aliasName) {

        Table anno = entityType.getAnnotation(Table.class);
        if (anno == null) {
            throw new ApplicationException("实体类" + entityType.getName() + " 未设置 @Table 注解");
        }
        String name = anno.name();
        this.fromClause = new FromClause(name).as(aliasName);

        this.selectEntity(aliasName);

        return this;
    }

    public QueryWrapper<T> from(String tableName, String alias) {
        this.fromClause = new FromClause(tableName).as(alias);
        return this;
    }

    public QueryWrapper<T> where(WhereClause clause) {
        if (whereClause != null) {
            throw new ApplicationException("where子句已经存在");
        }
        this.whereClause = clause;
        return this;
    }


    public QueryWrapper<T> where(Consumer<CompoundPredicate> process) {
        if (whereClause != null) {
            throw new ApplicationException("where子句已经存在");
        }
        CompoundPredicate predicate = new CompoundPredicate(Predicate.LinkOperator.AND);
        process.accept(predicate);
        this.whereClause = new WhereClause(predicate);
        if (whereClause.getParameters().size() > 0) {
            this.parameters.addAll(this.whereClause.getParameters());
        }
        return this;
    }

    public QueryWrapper<T> join(String tableName, String onClause) {
        this.innerJoin(tableName, onClause);

        return this;
    }

    public QueryWrapper<T> leftJoin(String tableName, String onClause) {
        JoinClause join = new JoinClause(tableName, JoinType.LEFT);
        join.on(onClause);
        this.joinTables.add(join);
        return this;
    }

    public QueryWrapper<T> leftJoin(String tableName, String alias, String onClause) {
        JoinClause join = new JoinClause(tableName, JoinType.LEFT);
        join.as(alias);
        join.on(onClause);
        this.joinTables.add(join);
        return this;
    }

    public QueryWrapper<T> rightJoin(String tableName, String onClause) {
        JoinClause join = new JoinClause(tableName, JoinType.RIGHT);
        join.on(onClause);
        this.joinTables.add(join);
        return this;
    }

    public QueryWrapper<T> innerJoin(String tableName, String onClause) {
        JoinClause join = new JoinClause(tableName, JoinType.INNER);
        join.on(onClause);
        this.joinTables.add(join);
        return this;
    }

    public QueryWrapper<T> groupBy(String... columns) {
        this.groupByCLause = new GroupByClause(columns);
        return this;
    }

    public QueryWrapper<T> groupBy(String columnObject) {
        this.groupByCLause = new GroupByClause(columnObject);
        return this;
    }

    public QueryWrapper<T> orderBy(String... columns) {
        this.orderByClause = new OrderByClause(columns);
        return this;
    }

    public QueryWrapper<T> orderBy(List<String> columns) {
        this.orderByClause = new OrderByClause(columns);
        return this;
    }

    public QueryWrapper<T> orderBy(String columnObject) {
        this.orderByClause = new OrderByClause(columnObject);
        return this;
    }

    public QueryWrapper<T> limit(Integer total) {
        this.offsetClause = new OffsetClause(0);
        this.limitClause = new LimitClause(total);
        return this;
    }

    public QueryWrapper<T> limit(Integer start, Integer total) {
        this.offsetClause = new OffsetClause(start);
        this.limitClause = new LimitClause(total);
        return this;
    }

    public QueryWrapper<T> limit(Long n, Long m) {
        this.offsetClause = new OffsetClause(n);
        this.limitClause = new LimitClause(m);
        return this;
    }

    /**
     * 返回一行数据
     * 不能大于1行或少于1行
     *
     * @return 数据记录
     */
    public T fetchSingle() {
        Optional<T> box = fetchOne();
        if (!box.isPresent()) {
            throw new ApplicationException("没有查到数据，无论如何需要返回1行");
        }
        return box.get();
    }

    /**
     * 获取一条
     * 最多返回一行
     *
     * @return 数据记录
     */
    public Optional<T> fetchAny() {
        List<T> objects = fetch();
        if (objects.size() == 0) {
            return Optional.empty();
        } else {
            return Optional.of(objects.get(0));
        }
    }


    /**
     * 返回一个对象
     * 返回行数超过1，抛出异常。
     *
     * @return 数据记录
     */
    public Optional<T> fetchOne() {

        String sql = this.toSentence();

        Session session = this.builder.getManager().unwrap(Session.class);
        NativeQuery<Map<String, Object>> query = session.createSQLQuery(sql);
        for (int i = 0; i < parameters.size(); i++) {
            Expression<?> expression = parameters.get(i);
            if (expression.getValue() instanceof String) {
                // 占位符索引从1开始
                query.setParameter(i + 1, String.valueOf(expression.getValue()));
            }

            if (expression.getValue() instanceof Integer) {
                // 占位符索引从1开始
                query.setParameter(i + 1, expression.getValue());
            }

        }
        List<Map<String, Object>> rows = query
                .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
                .getResultList();
        List<T> models = RowTransformer.toBeanList(rows, entityType);
        if (models.size() > 1) {
            throw new ApplicationException("匹配的行数大于1");
        }
        return models.size() == 1 ? Optional.of(models.get(0)) : Optional.empty();
    }

    /**
     * 返回列表
     * 属于通用方法，不管返回多少数据都用List接收
     *
     * @return 数据记录
     */
    public List<T> fetch() {
        String sql = this.toSentence();
        Session session = this.builder.getManager().unwrap(Session.class);
        NativeQuery<Map<String, Object>> query = session.createSQLQuery(sql);
        for (int i = 0; i < parameters.size(); i++) {
            Expression<?> expression = parameters.get(i);
            if (expression.getValue() instanceof String) {
                // 占位符索引从1开始
                query.setParameter(i + 1, String.valueOf(expression.getValue()));
            }

            if (expression.getValue() instanceof Integer) {
                // 占位符索引从1开始
                query.setParameter(i + 1, expression.getValue());
            }

        }
        List<Map<String, Object>> rows = query
                .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
                .list();
        return RowTransformer.toBeanList(rows, entityType);
    }

    protected Long countRows() {

        String sentence = this.toCountSentence();
        Session session = this.builder.getManager().unwrap(Session.class);
        NativeQuery<?> query = session.createSQLQuery(sentence);
        for (int i = 0; i < parameters.size(); i++) {
            Expression<?> expression = parameters.get(i);
            if (expression.getValue() instanceof String) {
                // 占位符索引从1开始
                query.setParameter(i + 1, String.valueOf(expression.getValue()));
            }

            if (expression.getValue() instanceof Integer) {
                // 占位符索引从1开始
                query.setParameter(i + 1, expression.getValue());
            }

        }
        return ((Number) query.uniqueResult()).longValue();
    }

    public PageList<T> fetchPage(Pageable pageable) {
        Long rowTotal = this.countRows();

        if (pageable.getSort() != null) {
            // 主表名
            this.orderBy(getFromClause().getReferenceName() + "." + CaseFormat.toUnderscore(pageable.getSort()) + " desc");
        }


        long pageSize = pageable.getPageSize();
        long pageNumber = pageable.getPageNumber();

        this.limit(pageable.getOffset(), pageSize);

        String pageSentence = this.getSentence();

        logger.info("SQL：" + pageSentence);

        List<Object> paramValues = new LinkedList<>();
        parameters.forEach(item -> {
            paramValues.add(item.getValue());
        });
        logger.info("parameters: " + JSON.toJSONString(paramValues));
        Session session = this.builder.getManager().unwrap(Session.class);
        NativeQuery<Map<String, Object>> query = session.createSQLQuery(pageSentence);
        for (int i = 0; i < parameters.size(); i++) {
            Expression<?> expression = parameters.get(i);
            if (expression.getValue() instanceof String) {
                // 占位符索引从1开始
                query.setParameter(i + 1, String.valueOf(expression.getValue()));
            }

            if (expression.getValue() instanceof Integer) {
                // 占位符索引从1开始
                query.setParameter(i + 1, expression.getValue());
            }

        }

        List<Map<String, Object>> rows = query
                .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP)
                .getResultList();
        List<T> models = RowTransformer.toBeanList(rows, entityType);
        return new PageList<>(pageSize, pageNumber, rowTotal, models);
    }

    /**
     * 获取主语句
     *
     * @return select主语
     */
    public String getSentence() {
        // 如果语句已经锁定，直接返回
        if (this.lockSentence) {
            return this.mainSentence;
        }
        this.lockSentence = true;
        this.mainSentence = this.toSentence();

        return this.mainSentence;
    }

    private String toCountSentence() {
        StringBuilder sentence = new StringBuilder();

        sentence.append("SELECT COUNT(1) total");
        sentence.append(" ").append(this.fromClause.render());
        if (Objects.nonNull(this.joinTables)) {
            for (JoinClause clause : joinTables) {
                sentence.append(" ").append(clause.render());
            }
        }

        if (Objects.nonNull(this.whereClause)) {
            String whereString = this.whereClause.render();
            // 没有条件就跳过
            if (!"".equals(whereString)) {
                sentence.append(" ").append(this.whereClause.render());
            }
        }
        if (Objects.nonNull(this.orderByClause)) {
            sentence.append(" ").append(this.orderByClause.render());
        }

        return sentence.toString();
    }

    /**
     * 生成主语句
     */
    public String toSentence() {
        StringBuilder sentence = new StringBuilder();

        sentence.append(this.selectClause.render());
        sentence.append(" ").append(this.fromClause.render());
        if (Objects.nonNull(this.joinTables)) {
            for (JoinClause clause : joinTables) {
                sentence.append(" ").append(clause.render());
            }
        }

        if (Objects.nonNull(this.whereClause)) {
            String whereString = this.whereClause.render();
            // 没有条件就跳过
            if (!"".equals(whereString)) {
                sentence.append(" ").append(this.whereClause.render());
            }
        }
        if (Objects.nonNull(this.orderByClause)) {
            sentence.append(" ").append(this.orderByClause.render());
        }

        if (Objects.nonNull(this.limitClause)) {
            sentence.append(" ").append(this.limitClause.render());
        }
        if (Objects.nonNull(this.offsetClause)) {
            sentence.append(" ").append(this.offsetClause.render());
        }

        return sentence.toString();
    }


}
